#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#if PTRDIFF_MAX > UINT32_MAX
#define ltoint(a) ((lua_Integer)(a))
#define ltoptr(a) ((void*)(a))
#else
#define ltoint(a) ((int)(a))
#define ltoptr(a) (void*)((int)a)
#endif

#if LUA_VERSION_NUM<502
#define lua_isinteger lua_isnumber
#endif

#ifndef lcheck_toptr
#define lcheck_toptr _lcheck_toptr
#define lua_isstring(a,idx) ((lua_type(a,idx)==LUA_TSTRING)?1:0)
static void* _lcheck_toptr(lua_State*L,int idx){
  void* data = 0;
  if(lua_isstring(L,idx)){
    data = (void*)lua_tostring(L,idx);
  }
  else if(lua_islightuserdata(L,idx)/*||lua_isuserdata(L,idx)*/){
    data = (void*)lua_touserdata(L,idx);
  }
  else if(lua_isuserdata(L,idx)){
    data = *(void**)lua_touserdata(L,idx);
  }
  else{
    lua_pushliteral(L,"type error:pointer_type");
    lua_error(L);
  };
  return data;
};
#endif

static int dr(lua_State*L){
  void* a1 = lcheck_toptr(L,1);
  int offset,size;
  lua_Integer ret;
  offset = (int)luaL_checkinteger(L,2);
  size = (int)luaL_checkinteger(L,3);
  a1 = (char*)a1 + offset;
  switch(size){
      case 1:ret = *(unsigned char*)a1;break;
      case 2:ret = *(unsigned short*)a1;break;
      case 4:ret = *(unsigned int*)a1;break;
      case 8:ret = *(uint64_t*)a1;break;
      default :ret = *(lua_Integer*)a1;break;
  };
  lua_pushinteger(L,ret);
  return 1;
}

static int dw(lua_State*L){
  void* a1 = lcheck_toptr(L,1);
  int offset,size;
  offset = (int)luaL_checkinteger(L,2);
  size = (int)luaL_checkinteger(L,3);
  lua_Integer value = luaL_checkinteger(L,4);
  a1 = (char*)a1 + offset;
  int b = 0;
  switch(size){
      case 1:*(unsigned char*)a1 = value;b = 1;break;
      case 2:*(unsigned short*)a1 = value;b=1;break;
      case 4:*(unsigned int*)a1 = value;b=1;break;
      case 8:*(uint64_t*)a1 = value;b=1;break;
      default :*(lua_Integer*)a1 = value;b=1;break;
  };
  lua_pushboolean(L,b);
  return 1;
}

static int dwpint(lua_State*L){
  uint32_t* a1 = lcheck_toptr(L,1);
  int top = lua_gettop(L);
  int idx = 2;
  for(;idx<=top;idx++){
    uint32_t v = luaL_checkinteger(L,idx);
    *a1++ = v;
  }
  return 0;
}

static int drpint(lua_State*L){
  uint32_t* a1 = lcheck_toptr(L,1);
  int count = luaL_checkinteger(L,2);
  int retc = 0;
  for(;retc<count;retc++){
    lua_pushinteger(L,*a1++);
  }
  return retc;
}

#define drptype(type,name,func) \
static int drp##name(lua_State*L){   \
  type* a1 = lcheck_toptr(L,1);        \
  /*int count = luaL_checkinteger(L,2);*/  \
  int count = 1;                       \
  if(lua_isinteger(L,2))                \
    count = lua_tointeger(L,2);         \
  int retc = 0;                        \
  for(;retc<count;retc++){             \
    func(L,*a1++);          \
  }                                    \
  return retc;                         \
}

#define dwptype(type,name,func)      \
static int dwp##name(lua_State*L){   \
  type* a1 = lcheck_toptr(L,1);      \
  int top = lua_gettop(L);           \
  int idx = 2;                       \
  for(;idx<=top;idx++){              \
    type v = func(L,idx);        \
    *a1++ = v;                       \
  }                                  \
  return 0;                         \
}

dwptype(uint64_t,long,luaL_checkinteger);
dwptype(uint8_t,char,luaL_checkinteger);
dwptype(uint16_t,short,luaL_checkinteger);
dwptype(float,float,luaL_checknumber);
dwptype(double,double,luaL_checknumber);

drptype(uint64_t,long,luaL_checkinteger);
drptype(uint8_t,char,lua_pushinteger);
drptype(uint16_t,short,lua_pushinteger);
drptype(float,float,lua_pushnumber);
drptype(double,double,lua_pushnumber);

#define dwpntype(type,name,func)     \
static int dwpn##name(lua_State*L){   \
  type* a1 = lcheck_toptr(L,1);      \
  int top = lua_gettop(L);           \
  int idx = 2;                       \
  for(;idx<=top;idx++){              \
    if(!lua_isnil(L,idx)){                  \
        type v = func(L,idx);       \
        *a1++ = v;                  \
    }                               \
        else a1++;                  \
  }                                 \
  return 0;                         \
}

dwpntype(uint64_t,long,luaL_checkinteger);
dwpntype(uint32_t,int,luaL_checkinteger);
dwpntype(uint8_t,char,luaL_checkinteger);
dwpntype(uint16_t,short,luaL_checkinteger);
dwpntype(float,float,luaL_checknumber);
dwpntype(double,double,luaL_checknumber);




static int dwp(lua_State*L){
  //dwp(ptr,offset,size,...)
  //dwp(ptr,offset,format,...)
  void* a1 = lcheck_toptr(L,1);
  int offset,size;
  const char*format = 0;
  offset = (int)luaL_checkinteger(L,2);
  if(lua_isinteger(L,3)){
    size = (int)luaL_checkinteger(L,3);
  }
  else if(lua_isstring(L,3)){
    format = lua_tostring(L,3);
  }
  else{
    lua_pushinteger(L,0);
    return 1;
  }
  int idx=4;
  if(format){
    char c;
    int retc = 0;
    uint8_t *data = a1;
    uint64_t v;
    while(c=*format++){
        if(!lua_isnil(L,idx)){
            switch(c){
                case 'l': v = luaL_checkinteger(L,idx);*(uint64_t*)data = v; data+= 8; break;
                case 'i': v = luaL_checkinteger(L,idx);*(uint32_t*)data = v; data+= 4; break;
                case 'h': v = luaL_checkinteger(L,idx);*(uint16_t*)data = v; data+= 2; break;
                case 'c':
                case 'b': v = luaL_checkinteger(L,idx);*(uint8_t*)data = v; data+= 1; break;
                case 'd': {double v = luaL_checknumber(L,idx);*(double*)data = v; data+= 8; }break;
                case 'f': {float  v = luaL_checknumber(L,idx);*(float*)data = v; data+= 4; }break;
                default:
                lua_error(L);
                break;
            }
        }
        idx ++;
    }
    return 0;
  }
  else{  //!format
      int top = lua_gettop(L);
      a1 = (void*)((uint8_t*)a1 + offset);
      for(;idx<=top;idx++){
          uint8_t *data = a1;
          data += ((size*(idx-4)));
          uint64_t v;
          int type = lua_type(L,idx);
          switch(type){
            case LUA_TNIL           :
                continue;
            break;
            case LUA_TNUMBER        :
                if(lua_isinteger(L,idx)){
                    v = lua_tointeger(L,idx);
                }
                else{
                    if(size>4){
                        float vv = lua_tointeger(L,idx);
                        v = *(uint32_t*)&vv;
                    }
                    else{
                        double vv = lua_tointeger(L,idx);
                        v = *(uint64_t*)&vv;
                    }
                }
            break;
            case LUA_TBOOLEAN       :
                v = lua_toboolean(L,idx);
            break;
            case LUA_TSTRING        :
                v = ltoint(lua_tostring(L,idx));
            break;
            case LUA_TUSERDATA      :
                v = ltoint(*(void**)lua_touserdata(L,idx));
            break;
            case LUA_TLIGHTUSERDATA :
                v = ltoint(lua_touserdata(L,idx));
            break;
            default:
                lua_pushstring(L,"error data type\n");
                lua_error(L);
                return 0;
            break;
            /*
            case LUA_TTABLE         :
            break;
            case LUA_TFUNCTION      :
            break;
            case LUA_TTHREAD        :
            break;
            */
          }
          #define _writeType(type,data,v) *(type*)data = (type)v
          switch(size){
              case 1:  _writeType(uint8_t,data,v); break;
              case 2:  _writeType(uint16_t,data,v); break;
              case 4:  _writeType(uint32_t,data,v); break;
              case 8:  _writeType(uint64_t,data,v); break;
              default:
              memcpy(data,&v,size);
              break;
          }
      }
  }
}

static int drp(lua_State*L){
  //drp(ptr,offset,size[,count=1])
  //drp(ptr,offset,format);
  const char*format = 0;
  int count = 1;
  void* a1 = lcheck_toptr(L,1);
  int offset,size;
  lua_Integer ret;
  offset = (int)luaL_checkinteger(L,2);
  a1 = (char*)a1 + offset;

  if(lua_isinteger(L,3)){
      size = (int)luaL_checkinteger(L,3);
      if(lua_isinteger(L,4)){
        count = lua_tointeger(L,4);
      }
  }
  else if(lua_isstring(L,3)){
      format = lua_tostring(L,3);
  }
  else{
      return 0;
  }

  if(!format){
      int retc = 0;
      switch(size){
          case 1:{
                uint8_t *data = a1;
                while(count-->0){
                  ret = *data;
                  lua_pushinteger(L,*data);
                  data ++;
                  retc ++;
                }
            }
            #define looppush(type) {\
            type *data = a1;    \
            while(count-->0){        \
              ret = *data;         \
              lua_pushinteger(L,*data); \
              data ++;                  \
              retc ++;                  \
            }}

          break;
          case 2:looppush(uint16_t);break;
          case 4:looppush(uint32_t);break;
          case 8:looppush(uint64_t);break;
          default :looppush(uint64_t);break;
      };
      return retc;
  }
  else{
    char c;
    int retc = 0;
    uint8_t *data = a1;
    while(c=*format++){
        switch(c){
            case 'i':lua_pushinteger(L,*(uint32_t*)data);data+=4;retc++;break;
            case 'h':lua_pushinteger(L,*(uint16_t*)data);data+=2;retc++;break;
            case 'c':
            case 'b':lua_pushinteger(L,*(uint8_t*)data); data+=1;retc++;break;
            case 'd':lua_pushinteger(L,*(double*)data);  data+=8;retc++;break;
            case 'f':lua_pushinteger(L,*(float*)data);   data+=4;retc++;break;

            default:
            lua_pushnil(L);
            break;
        }
    }
    return retc;
  }
}

static int drstring(lua_State*L){
  void* a1 = lcheck_toptr(L,1);
  int offset,size;
  offset = luaL_checkinteger(L,2);
  size = luaL_checkinteger(L,3);
  lua_pushlstring(L,(const char*)a1 + offset,size);
  return 1;
}

static int dwstring(lua_State*L){
  void* a1 = lcheck_toptr(L,1);
  int offset,size;
  offset = luaL_checkinteger(L,2);
  size = luaL_checkinteger(L,3);
  const char*s = luaL_checkstring(L,4);
  if(memcpy((char*)a1 + offset,s,size)){
      lua_pushinteger(L,size);
      return 1;
  }
  return 0;
}

static int dwfloat(lua_State*L){
  void* a1 = lcheck_toptr(L,1);
  int offset;
  float value;
  offset = luaL_checkinteger(L,2);
  value = (float)luaL_checknumber(L,3);
  *(float*)((char*)a1 + offset) = value;
  lua_pushboolean(L,1);
  return 1;
}

static int dwdouble(lua_State*L){
  void* a1 = lcheck_toptr(L,1);
  int offset;
  double value;
  offset = luaL_checkinteger(L,2);
  value = (double)luaL_checknumber(L,3);
  *(double*)((char*)a1 + offset) = value;
  lua_pushboolean(L,1);
  return 1;
}

static int drfloat(lua_State*L){
  void* a1 = lcheck_toptr(L,1);
  int offset = luaL_checkinteger(L,2);
  float value;
  value = *(float*)((char*)a1 + offset);
  lua_pushnumber(L,value);
  return 1;
}

static int drdouble(lua_State*L){
  void* a1 = lcheck_toptr(L,1);
  int offset = luaL_checkinteger(L,2);
  double value;
  value = *(double*)((char*)a1 + offset);
  lua_pushnumber(L,value);
  return 1;
}

static int dpointer(lua_State*L){
  void* a1 = 0;
  //如果先使用lua_isstring ,则integer会被判断字符串类型
  if(lua_isinteger(L,1)){
    lua_Integer v = lua_tointeger(L,1);  //waring
    a1 = ltoptr(v);
  }
  else{
    a1 = lcheck_toptr(L,1);
  }
  int offset = 0;
  if(lua_isinteger(L,2)){
      offset = lua_tointeger(L,2);
  }
  lua_pushlightuserdata(L,(char*)a1 + offset);
  return 1;
}

static int dint(lua_State*L){
  void* a1 = 0;
  if(lua_isinteger(L,1)){
    lua_Integer v = lua_tointeger(L,1);  //waring
    a1 = ltoptr(v);
  }
  else{
    a1 = lcheck_toptr(L,1);
  }
  lua_Integer v = ltoint(a1);
  lua_pushinteger(L,v);
  return 1;
}

static int touserdata(lua_State*L){
  void* a1 = lcheck_toptr(L,1);

  void ** p = lua_newuserdata(L,sizeof(void*));
  *p = a1;
  return 1;
}

static int islightuserdata(lua_State*L){
    lua_pushboolean(L,lua_islightuserdata(L,1));
    return 1;
}

static int isuserdata(lua_State*L){
    lua_pushboolean(L,!lua_islightuserdata(L,1) && lua_isuserdata(L,1));
    return 1;
}

static int isinteger(lua_State*L){
    lua_pushboolean(L,lua_isinteger(L,1));
    return 1;
}

static int istable(lua_State*L){
  lua_pushboolean(L,lua_istable(L,1));
  return 1;
}

static int isstring(lua_State*L){
  lua_pushboolean(L,lua_isstring(L,1));
  return 1;
}

static int iscfunction(lua_State*L){
  lua_pushboolean(L,lua_iscfunction(L,1));
  return 1;
}

static int isnull(lua_State*L){
  void* a1 = 0;
  if(lua_isinteger(L,1)){
    lua_Integer v = lua_tointeger(L,1);  //waring
    a1 = ltoptr(v);
  }
  else{
    a1 = lcheck_toptr(L,1);
  }
  if(a1){
    lua_pushboolean(L,0);
  }
  else{
    lua_pushboolean(L,1);
  }
  return 1;
}

static int gettype(lua_State*L){
  lua_pushinteger(L,lua_type(L,1));
  return 1;
}

static int udsetmetatable(lua_State*L){
    //userdata
    if(lua_isuserdata(L,1)){
        if (luaL_getmetafield(L, 1, "__metatable") != LUA_TNIL)
            return luaL_error(L, "cannot change a protected metatable");
        lua_settop(L,2);
        lua_setmetatable(L,1);
    }
    return 1;
}

static int dmalloc(lua_State*L){
    int a1 = luaL_checkinteger(L,1);
    lua_pushlightuserdata(L,malloc(a1));
    return 1;
}

static int dcalloc(lua_State*L){
    int a1 = luaL_checkinteger(L,1);
    int a2 = luaL_checkinteger(L,2);
    lua_pushlightuserdata(L,calloc(a1,a2));
    return 1;
}

static int dfree(lua_State*L){
    void*a1 = lcheck_toptr(L,1);
    free(a1);
    return 0;
}

static int dmemcpy(lua_State*L){
    void*a1 = lcheck_toptr(L,1);
    void*a2 = lcheck_toptr(L,2);
    int size = luaL_checkinteger(L,3);
    void* ret = memcpy(a1,a2,size);
    if(ret){
        lua_pushboolean(L,1);
    }
    else{
        lua_pushboolean(L,0);
    }
    return 1;
}

static int dmemset(lua_State*L){
    void*a1 = lcheck_toptr(L,1);
    int v = luaL_checkinteger(L,2);
    int size = luaL_checkinteger(L,3);
    if(memset(a1,v,size))
        lua_pushboolean(L,1);
    else
        lua_pushboolean(L,0);
    return 1;
}

static int dtostring(lua_State*L){
    void*a1 = lcheck_toptr(L,1);
    lua_pushstring(L,a1);
    return 1;
}

int luaopen_drw(lua_State*L){
  lua_register(L,"dw",dw);
  lua_register(L,"dr",dr);
  lua_register(L,"dwp",dwp);
  lua_register(L,"drp",drp);

  lua_register(L,"drplong",drplong);
  lua_register(L,"drpint",drpint);
  lua_register(L,"drpchar",drpchar);
  lua_register(L,"drpshort",drpshort);
  lua_register(L,"drpdouble",drpdouble);
  lua_register(L,"drpfloat",drpfloat);
  lua_register(L,"dwplong",dwplong);
  lua_register(L,"dwpint",dwpint);
  lua_register(L,"dwpchar",dwpchar);
  lua_register(L,"dwpshort",dwpshort);
  lua_register(L,"dwpdouble",dwpdouble);
  lua_register(L,"dwpfloat",dwpfloat);
  lua_register(L,"dwpnlong",   dwpnint);
  lua_register(L,"dwpnint",   dwpnint);
  lua_register(L,"dwpnchar",  dwpnchar);
  lua_register(L,"dwpnshort", dwpnshort);
  lua_register(L,"dwpndouble",dwpndouble);
  lua_register(L,"dwpnfloat", dwpnfloat);



  lua_register(L,"drstring",drstring);
  lua_register(L,"dwstring",dwstring);
  lua_register(L,"dpointer",dpointer);
  lua_register(L,"dint",dint);
  lua_register(L,"udsetmetatable",udsetmetatable);
  lua_register(L,"drfloat",drfloat);
  lua_register(L,"drdouble",drdouble);
  lua_register(L,"dwfloat",dwfloat);
  lua_register(L,"dwdouble",dwdouble);
  lua_register(L,"dmalloc",dmalloc);
  lua_register(L,"dcalloc",dcalloc);
  lua_register(L,"dfree",dfree);
  lua_register(L,"dmemcpy",dmemcpy);
  lua_register(L,"dmemset",dmemset);
  lua_register(L,"dtostring",dtostring);
  lua_register(L,"dtouserdata",touserdata);

  lua_register(L,"touserdata",touserdata);
  lua_register(L,"islightuserdata",islightuserdata);
  lua_register(L,"isuserdata",isuserdata);
  lua_register(L,"isinteger",isinteger);
  lua_register(L,"istable",istable);
  lua_register(L,"isstring",isstring);
  lua_register(L,"iscfunction",iscfunction);
  lua_register(L,"isnull",isnull);
  lua_register(L,"gettype",gettype);


  return 0;
};
